use crate::utils::{struct_all_types, Ty};
use proc_macro::TokenStream;
use proc_macro2::TokenStream as T2;
use quote::{format_ident, quote};
use syn::{parse_macro_input, DeriveInput, Ident};

use super::utils::TypeInfo;

impl TypeInfo {
    fn build(&self, check_flag: bool) -> T2 {
        let sc = match self.ty {
            Ty::BYTES => self.build_bytes(),
            Ty::U8 | Ty::I8 => self.build8(),
            Ty::U16 | Ty::I16 => self.build16(),
            Ty::U32 | Ty::I32 => self.build32(),
        };
        if !check_flag {
            return sc;
        }
        let ty_size = self.ty.size();
        let check = quote! {
            if buf.len() < (index + #ty_size) {
                return None;
            }
            #sc
        };
        check
    }

    fn build_fixed(&self, index: usize, lens: &mut Vec<Ident>) -> T2 {
        let sc = match self.ty {
            Ty::BYTES => self.build_bytes_fixed(index, lens),
            Ty::U8 | Ty::I8 => self.build8_fixed(index, lens),
            Ty::U16 | Ty::I16 => self.build16_fixed(index, lens),
            Ty::U32 | Ty::I32 => self.build32_fixed(index, lens),
        };
        sc
    }

    fn build8(&self) -> T2 {
        let name = &self.name;
        quote! {
            unsafe {
                *buf.get_unchecked_mut(index) = self.#name as u8;
            }
            index += 1;
        }
    }

    fn build8_fixed(&self, index: usize, lens: &mut Vec<Ident>) -> T2 {
        let name = &self.name;
        quote! {
            *buf.get_unchecked_mut(#index #(+#lens)*) = self.#name as u8;
        }
    }

    fn build16(&self) -> T2 {
        let name = &self.name;
        quote! {
            unsafe {
                *buf.get_unchecked_mut(index) = (self.#name >> 8) as u8;
                *buf.get_unchecked_mut(index + 1) = self.#name as u8;
            }
            index += 2;
        }
    }

    fn build16_fixed(&self, index: usize, lens: &mut Vec<Ident>) -> T2 {
        let name = &self.name;
        quote! {
            *buf.get_unchecked_mut(#index #(+#lens)*) = (self.#name >> 8) as u8;
            *buf.get_unchecked_mut(#index + 1 #(+#lens)*) = self.#name as u8;
        }
    }

    fn build32(&self) -> T2 {
        let name = &self.name;
        quote! {
            unsafe {
                *buf.get_unchecked_mut(index) = (self.#name >> 24) as u8;
                *buf.get_unchecked_mut(index + 1) = (self.#name >> 16) as u8;
                *buf.get_unchecked_mut(index + 2) = (self.#name >> 8) as u8;
                *buf.get_unchecked_mut(index + 3) = self.#name as u8;
            }
            index += 4;
        }
    }

    fn build32_fixed(&self, index: usize, lens: &mut Vec<Ident>) -> T2 {
        let name = &self.name;
        quote! {
            *buf.get_unchecked_mut(#index #(+#lens)*) = (self.#name >> 24) as u8;
            *buf.get_unchecked_mut(#index + 1 #(+#lens)*) = (self.#name >> 16) as u8;
            *buf.get_unchecked_mut(#index + 2 #(+#lens)*) = (self.#name >> 8) as u8;
            *buf.get_unchecked_mut(#index + 3 #(+#lens)*) = self.#name as u8;
        }
    }

    fn build_bytes(&self) -> T2 {
        let name = &self.name;
        let name_len = format_ident!("{}_len", name);
        quote! {
            unsafe {
                let #name_len = self.#name.len();
                *buf.get_unchecked_mut(index) = #name_len as u8;
                if buf.len() < (index + 1 + #name_len) {
                    return None;
                }
                ::core::ptr::copy_nonoverlapping(self.#name.as_ptr(), buf.as_mut_ptr().add(index + 1), #name_len);
                index += 1 + #name_len;
            }
        }
    }

    fn build_bytes_fixed(&self, index: usize, lens: &mut Vec<Ident>) -> T2 {
        let name = &self.name;
        let name_len = format_ident!("{}_len", name);
        let expand = quote! {
            let #name_len = self.#name.len();
            *buf.get_unchecked_mut(#index #(+#lens)*) = #name_len as u8;
            ::core::ptr::copy_nonoverlapping(self.#name.as_ptr(), buf.as_mut_ptr().add(#index #(+#lens)* + 1), #name_len);
        };
        lens.push(name_len.clone());
        expand
    }
}

pub fn build(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    let (impl_life, type_life, _where_life) = input.generics.split_for_impl();
    let fields = struct_all_types(&input);

    let mut min_size: usize = 0;
    for field in fields.iter() {
        min_size += field.ty.size();
    }

    let mut check_flag = false;
    let mut builds = quote! {};
    let mut builds_fixed = quote! {};
    let mut index_fixed: usize = 0;
    let mut lens = Vec::new();
    for field in fields.iter() {
        builds.extend(field.build(check_flag));
        if field.is_bytes() {
            check_flag = true
        }
        builds_fixed.extend(field.build_fixed(index_fixed, &mut lens));
        index_fixed += field.ty.size();
    }

    let expand = quote! {

        impl #impl_life Encode for #name #type_life {

            const ENCODE_MIN_LEN: usize = #min_size;

            fn to_bytes(&self, buf: &mut [u8]) -> Option<usize> {
                if buf.len() < #min_size {
                    return None;
                }
                let mut index = 0;
                #builds
                Some(index)
            }

            unsafe fn to_bytes_unsafe(&self, buf: &mut [u8]) -> usize {
               
                #builds_fixed
                
                #index_fixed #(+#lens)*
            }
        }
    };
    expand.into()
}
